package com.ant; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Vector; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; import java.net.URI; import java.nio.charset.MalformedInputException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.security.SecureRandom; import java.io.File; public class StringEncrypt extends Task { private String destDir; private Vector filesets = new Vector(); private Vector paths = new Vector(); static SecureRandom random = new SecureRandom(); class EncodedTuple { String method; byte[] ebytes; byte[] kbytes; } ArrayList<EncodedTuple> encodedTuples = new ArrayList<EncodedTuple>(); private String baseDir; private String mFile; private boolean verbose = true; private String normalizePath(String file){ File f = new File(file); String cleanFile = f.toURI().getPath(); return cleanFile; } public void setDest(String dest) { String cleanDir = normalizePath(dest); logInfo("setDest: " + cleanDir); this.destDir = cleanDir; } public void setBaseDir(String dir) { String cleanDir = normalizePath(dir); logInfo("setBaseDir: " + cleanDir); this.baseDir = cleanDir; } public void setMFile(String mfile) { String cleanDir = normalizePath(mfile); logInfo("setMFile: " + cleanDir); this.mFile = cleanDir; } public void setVerbose(boolean verbose) { this.verbose = verbose; } public void addFileset(FileSet fileset) { logInfo("addFileset: " + fileset); filesets.add(fileset); } public void addPath(Path path) { logInfo("addPath: " + path); paths.add(path); } /** * Entry point, per ogni file del path (passato come argomento nel * build.xml) viene creato un file encoded con le stringhe cifrate. Al * termine viene generato un file M.java con tutti i metodi relativi alle * stringhe. */ public void execute() { File userdir = new File(System.getProperty("user.dir")); String dir = userdir.toURI().getPath(); logInfo("execute: " + dir, true); for (Iterator itPaths = paths.iterator(); itPaths.hasNext();) { Path path = (Path) itPaths.next(); String[] includedFiles = path.list(); for (int i = 0; i < includedFiles.length; i++) { URI fileUri = (new File(includedFiles[i])).toURI(); String filename = fileUri.getPath().replace(dir + "/", ""); File destfile = new File(destDir + "/" + filename.replace(baseDir, "")); logInfo(" encode: " + filename + " -> " + destfile); mkdir(destfile.getParent()); try { // viene creato un nuovo file con la codifica delle stringhe encodeFile(filename, destfile.getAbsolutePath()); } catch (IOException ex) { ex.printStackTrace(); logInfo(ex.toString()); } } } // logInfo("Decoding class: " + mFile); // istanza che si occupa di generare il file M.java DecodingClass dc = new DecodingClass(destDir + "/com/android/m/M.java", mFile); for (EncodedTuple tuple : encodedTuples) { dc.append(tuple.method, tuple.ebytes, tuple.kbytes); } dc.close(); } private void mkdir(String parent) { File dir = new File(parent); dir.mkdirs(); } /** * Encoda tutte le stringhe nella forma M.e("...") presenti nel codice. * * @param input * : filename to encode * @param output * : filename encoded * @return true if ok * @throws IOException */ public boolean encodeFile(String input, String output) throws IOException { byte[] bytes = new byte[128 * 1024]; // large enough FileInputStream in = new FileInputStream(input); int bytesRead = in.read(bytes); in.close(); String content = new String(bytes, 0, bytesRead, "ISO8859-1"); content = encodedContents(content); bytes = content.getBytes("ISO8859-1"); FileOutputStream fout = new FileOutputStream(output); fout.write(bytes); fout.close(); return true; } /** * Encoda un contenuto, riconoscendo il pattern di una stringa. Ogni stringa * viene caricata in una lista, che poi viene usata per la generazione di * M.java * * @param contents * : stringa del contenuto da encodare * @return */ public String encodedContents(String contents) { String reg = "M.e\\(\"((?:\"|.)*?)\"\\)"; Pattern p = Pattern.compile(reg, Pattern.MULTILINE); Matcher m = p.matcher(contents); StringBuffer sb = new StringBuffer(); while (m.find()) { // il gruppo 1 e' la stringa pura String text = m.group(1); // m.appendReplacement(sb,"\"" + text +"\""); m.appendReplacement(sb, polymorph(text)); // logInfo(" string: " + text); } m.appendTail(sb); return sb.toString(); } private String polymorph(String text) { byte[] pbytes = text.getBytes(); byte[] ebytes = new byte[ pbytes.length + 2 ]; byte[] kbytes = new byte[ pbytes.length + 2 + random.nextInt(30) ]; random.nextBytes(kbytes); enc(ebytes, pbytes, kbytes); String method = "d_" + Math.abs(random.nextLong()); appendStringDecode(method, ebytes, kbytes); byte[] te = copy(ebytes); dec(te, kbytes); assert te.length == pbytes.length + 2; for (int i = 0; i < te.length - 2; i++) { assert te[i] == pbytes[i]; } return "M." + method + "(\"KN" + Utils.byteArrayToHexString(ebytes) + "\")"; } private void enc(byte[] ebytes, byte[] pbytes, byte[] kbytes) { int diff = kbytes.length - pbytes.length; assert diff > 0; ebytes[0] = (byte) random.nextInt(); ebytes[1] = (byte) random.nextInt(); for (int i = 0; i < pbytes.length; i++) { ebytes[i+2] = (byte) (pbytes[i] ^ kbytes[i+1] ^ 0x42); } } public static String dec(byte[] ebytes, byte[] kbytes) { for (int i = 0; i < ebytes.length - 2; i++) { ebytes[i] = (byte) (ebytes[i + 2] ^ kbytes[i + 1] ^ 0x42); } String value = new String(ebytes, 0, ebytes.length - 2); return value; } // COMPAT public static byte[] copy(final byte[] payload, final int offset, final int length) { final byte[] buffer = new byte[length]; System.arraycopy(payload, offset, buffer, 0, length); return buffer; } public static byte[] copy(final byte[] ct) { return copy(ct, 0, ct.length); } private void appendStringDecode(String method, byte[] ebytes, byte[] kbytes) { EncodedTuple tuple = new EncodedTuple(); tuple.method = method; tuple.ebytes = ebytes; tuple.kbytes = kbytes; encodedTuples.add(tuple); } private static String enc(String text) { byte[] ebytes = text.getBytes(); byte[] obytes = new byte[ebytes.length]; random.nextBytes(obytes); for (int i = 0; i < obytes.length; i++) { ebytes[i] = (byte) (ebytes[i] ^ obytes[i]); } return new String(ebytes); } private void logInfo(String message) { logInfo(message,false); } private void logInfo(String message, boolean forced) { if (this.getProject() != null) { // we are running in ant, so use ant if (verbose || forced) { // log this.log(message, Project.MSG_INFO); } } else { // we are running outside of ant, log to System.out System.out.println(message); } } }